package rsvp;

import java.awt.FontMetrics;
import java.awt.Label;
import java.awt.TextArea;

import rsvp.util.Average;
import rsvp.util.referencing.IntegerReference;

/**
 * @author ksz
 */
public class TextTickerThread extends Thread {
	
	private final String NEW_PARAGRAPH_SEQUENCE = "||";
	private static final boolean ADD_EXTRA_SEPARATION_IN_THE_LAST_LINE = true;


	private static final double F_WaitInSentece = 0.6;
	private static final double F_WaitNewSentece = 1.2;
	private static final double F_WaitNewParagraph = 3;


	private Main_RSVP m_app;	
	private ReaderStateObject m_state;
	private TextArea m_taWordDisplay;
	private Label m_tlStatus;
	private int m_iMinWordLength;
	
	
	/**
	 * @param app
	 * @param max
	 */
	public TextTickerThread(Main_RSVP app) {
		this.m_app = app;		
		initialize();
	}


	/**
	 * @param m_app
	 */
	public void initialize()
	{
		m_iMinWordLength = IntegerReference.parseInt(m_app.minWordLength.getText(), 9);
		
		m_taWordDisplay = m_app.getWordDisplay();
		m_tlStatus = m_app.getStatusLabel();
		m_state = m_app.state;
		
		if (m_state.sArBuffer.length != m_app.iDisplayNumberOfLines)
			m_state.sArBuffer = new String[m_app.iDisplayNumberOfLines];
	}


	/**
	 * Display the text.
	 * 
	 * @see java.lang.Thread#run()
	 */
	public void run() {
		
		// restart it if necessary
		startTextTick();		
		m_app.updateControls(true);
	}

	/**
	 * @param text
	 */
	private void startTextTick() {

		// initialize text and position
		String text = m_app.getTextInput().getText();
		int iCurrentTextPositionChars = m_app.getTextInput().getCaretPosition();
		iCurrentTextPositionChars += m_app.getTextInput().getSelectedText().length();
		
		// make sure that the text is fixed
		String text2 = Main_RSVP.fixText(text);		
		if (!text.equals(text2)) {		
//			compareText(text, text2);				
			m_app.getTextInput().setText(text2);  	
			m_app.getTextInput().setCaretPosition(iCurrentTextPositionChars);
			text = text2;
		}		

		m_app.state.avg.checkIfReadStatisticResetIsRequired(text);

		// get statistics
		int iTextLength = text.length();
		// discover standard word delay so app.wordsPerMinute.getText()
		// will match more closely the real WPM
		double iStandardOneWordDelay = 60000.0 / Integer.parseInt(m_app.wordsPerMinute.getText());

		calculateAverageWordLength(text, iTextLength, iStandardOneWordDelay, iCurrentTextPositionChars);
		
		printStatistic(text, iTextLength, iCurrentTextPositionChars,
						m_state.avg, iStandardOneWordDelay);

		// show the ticker text
		processTextUntilStopped(text, iCurrentTextPositionChars,
								iStandardOneWordDelay, false);

		if (m_app.isStarted()) {
			m_app.getTextInput().setCaretPosition(0);
			m_app.state.avg.resetReadStatistics();
		}
		m_app.bttStartStop.setLabel("Stop");
	}


//	private void compareText(String text, String text2)
//	{
//		for(int i = 0; i < text.length(); i++) {
//			if (text.charAt(i) != text2.charAt(i)) {
//				System.out.println("i = " + i);
//				System.out.println("text1 : " + text.substring(i - 10, i + 10));
//				System.out.println("text2 : " + text2.substring(i - 10, i + 10));
//				System.out.println("break");
//				break;
//			}
//		}
//	}

	/**
	 * @param text
	 * @param iTextLength
	 * @param iCurrentTextPositionChars
	 * @param avgWordLength
	 * @param iStandardOneWordDelay
	 */
	private void printStatistic(String text, int iTextLength,
			int iCurrentTextPositionChars, AverageTextStatistics avgWordLength,
			double iStandardOneWordDelay) {

		avgWordLength.iRemainingWords = (int) ((iTextLength - iCurrentTextPositionChars) / avgWordLength.lfAverage_IndividualWordLength);

		avgWordLength.setFileLocation(m_app.getReadFileLocation());
		
//		avgWordLength
//				.setTimeDelayedInTotal(processTextUntilStopped(
//																text,
//																iTextLength,
//																iCurrentTextPositionChars,
//																avgWordLength,
//																iStandardWordDelay,
//																true));
		avgWordLength.printVerboseStatistics_onStart();

		m_tlStatus.setText(avgWordLength.getPrintableLabelStatistic());
	}

	/**
	 * @param text
	 * @param iTextLength
	 * @param iCurrentTextPositionChars
	 * @param iStandardOneWordDelay
	 * @param bSimualted
	 * 
	 * @return lTimeDelayedInTotal
	 */
	private double processTextUntilStopped(String text, 
											int iCurrentTextPositionChars,
											double iStandardOneWordDelay,
											boolean bSimualted) {

		// average out the text width
		Average mathAvg = new Average();
		int iTextLength = text.length();
		
		// do this until end or stop is requested
		while (iCurrentTextPositionChars < iTextLength && m_app.isStarted()) {
	
			int iWordEnd = displayNextWord(
								text,										
								iCurrentTextPositionChars,															
								bSimualted);

			// wait, get selected words, run wait on it
			final String sSelection = convertTextFoundToPrintable(
													text,
													iCurrentTextPositionChars,
													iWordEnd);
			delayBetweenTicks(sSelection, m_state.avg, iStandardOneWordDelay,
								bSimualted);

			// update end position
			if (iCurrentTextPositionChars >= iWordEnd)
				iCurrentTextPositionChars++;
			else {
				mathAvg.add(iWordEnd - iCurrentTextPositionChars);
				iCurrentTextPositionChars = iWordEnd;
			}
			
		}
		return mathAvg.getAverage();
	}

	/**
	 * @param iCurrentTextPositionChars
	 * @param avg.lfAverageWordLength
	 * @param iStandardOneWordDelay
	 * @param iWordEnd
	 * @param bSimulate
	 * @return
	 */
	private void delayBetweenTicks(String sWord,
			AverageTextStatistics avg, double iStandardOneWordDelay, 
			boolean bSimulate) {


		double lfDelayPerWords;
		boolean bDynamicWordDelay = false;
		if (bDynamicWordDelay) 
		{
			lfDelayPerWords = getDynamicDelay(sWord, avg, iStandardOneWordDelay);
		} else
		{
			lfDelayPerWords = getLessDynamicDelay(sWord, avg, iStandardOneWordDelay);
		}
		
		
		try {
			if (!bSimulate)
				sleep((int) lfDelayPerWords);
		} catch (InterruptedException ie) {}
		
	}


	/**
	 * @param sWord
	 * @param avg
	 * @param iStandardOneWordDelay
	 * 
	 * @return
	 */
	private double getLessDynamicDelay(String sWord, AverageTextStatistics avg,
										double iStandardOneWordDelay)
	{
		final double lfMinDelay = (avg.lfAverage_WordDisplayLength / avg.lfAverage_IndividualWordLength) * iStandardOneWordDelay;
		double lfDelayPerWords = lfMinDelay;
		
		
		if (sWord.contains("\r")
				|| sWord.contains("\n")
				|| sWord.contains(NEW_PARAGRAPH_SEQUENCE)
			)
			lfDelayPerWords += F_WaitNewParagraph * lfMinDelay;
		
		return lfDelayPerWords ;
	}


	/**
	 * @param sWord
	 * @param avg
	 * @param iStandardOneWordDelay
	 * @return
	 */
	private double getDynamicDelay(String sWord, final AverageTextStatistics avg,
									double iStandardOneWordDelay)
	{
		
		double lfMinDelay = (m_iMinWordLength / avg.lfAverage_IndividualWordLength) * iStandardOneWordDelay;
		lfMinDelay *= 0.6;
		
		
		double lfDelayPerWords = (sWord.length() / avg.lfAverage_IndividualWordLength)
				* iStandardOneWordDelay;
		
		if (sWord.contains(",")
				|| sWord.contains(",")
				|| sWord.contains(";")
				|| sWord.contains("\"")
				|| sWord.contains("(")
				|| sWord.contains(")")
			)
			lfDelayPerWords += F_WaitInSentece * lfMinDelay;
		
		if (sWord.contains(".")
				|| sWord.contains(":")
				|| sWord.contains("?")
				|| sWord.contains("!"))
			lfDelayPerWords += F_WaitNewSentece * lfMinDelay;
		
		if (sWord.contains("\r")
				|| sWord.contains("\n")
				|| sWord.contains("|")
			)
			lfDelayPerWords += F_WaitNewParagraph * lfMinDelay;
		return lfDelayPerWords;
	}

	/**
	 * @param sText
	 * @param iTextLength
	 * @param iStandardOneWordDelay 
	 * @param iCurrentTextPositionChars 
	 * @return
	 */
	private AverageTextStatistics calculateAverageWordLength(String sText,
			int iTextLength, double iStandardOneWordDelay, int iCurrentTextPositionChars) {

		int iNumberOfWords = 0;
		for (int i = 0; i < iTextLength; i++)
			if (sText.charAt(i) == ' ')
				iNumberOfWords++;
		final double lfAverage = (double) iTextLength / iNumberOfWords;

		this.m_state.avg.iNumberOfWords = iNumberOfWords;
		this.m_state.avg.lfAverage_IndividualWordLength = lfAverage;
		this.m_state.avg.lfAverage_WordDisplayLength = processTextUntilStopped(
																		sText,																		
																		iCurrentTextPositionChars,																		
																		iStandardOneWordDelay,
																		true);
		
		return this.m_state.avg;
	}

	/**
	 * @param text
	 * @param iTextLength
	 * @param iCurrentTextPositionChars
	 * @param iStandardOneWordDelay
	 * @return
	 */
	private int displayNextWord(String text,
			int iCurrentTextPositionChars, boolean bSimulate) {

		AverageTextStatistics avg = m_state.avg;
		
		int iTextLength = text.length();
		int iSelectionEnd = findNextSelectionEnd(text, iTextLength, iCurrentTextPositionChars, m_iMinWordLength);
		String words = convertTextFoundToPrintable(text,
				iCurrentTextPositionChars, iSelectionEnd);
		iSelectionEnd = iCurrentTextPositionChars + words.length();
		
		if (!bSimulate) {
			// Display the word(s).
			m_taWordDisplay.setText(prepareListOfWords(words.toString()));

			// textInput.requestFocusInWindow();
			highlightText(findSelectionBegining(text,
			                                    iSelectionEnd,
												m_state.sArBuffer), 
						  iSelectionEnd);
			
			avg.iRemainingWords = (int) ((iTextLength - iSelectionEnd) / avg.lfAverage_IndividualWordLength);
			m_tlStatus.setText(avg.getPrintableLabelStatistic());
			
//			if (!taTextInput.getSelectedText().equals(words.toString())) {
//				System.out.println("------------------------words: " + words);
//				System.out.println("taTextInput.getSelectedText(): " + taTextInput.getSelectedText());
//			}
			
			m_app.repaint();
		}

		return iSelectionEnd;
	}


	/**
	 * Based on current position and list of strings, find the beginning of the selection
	 * 
	 * @param iCurrentTextPositionChars
	 * @return
	 */
	private int findSelectionBegining(String text,
										int iSearchPos,
										String[] arListDisplayedAsTicker)
	{
		
		for(int i = arListDisplayedAsTicker.length - 1; i >= 0; i--) {
			String s = arListDisplayedAsTicker[i];
			
			int iNewPos = text.lastIndexOf(s, iSearchPos);
			// check if the text structure is not normal;
			if (iNewPos == -1) 
				continue;
			if ((iSearchPos - iNewPos) > 5*m_iMinWordLength) 
				continue;
			// if a normal find is made, update iSearchPos
			iSearchPos = iNewPos;
		}

		return iSearchPos;
	}


	/**
	 * @param iSelectionStart
	 * @param iSelectionEnd
	 */
	private void highlightText(int iSelectionStart, int iSelectionEnd)
	{
		m_app.getTextInput().requestFocus();
		m_app.getTextInput().setSelectionStart(iSelectionStart);
		m_app.getTextInput().setSelectionEnd(iSelectionEnd);
	}

	/**
	 * Prepares the list of words to be displayed 
	 * by updating the array list of strings state.sArBuffer[] 
	 * 
	 * @param sNewString
	 * 
	 * @return
	 */
	private String prepareListOfWords(String sNewString) {		
		
		// if last line contains || then shift up with 2
		String sLast = m_state.sArBuffer[m_state.sArBuffer.length - 1];
		if (sLast != null && sLast.contains(NEW_PARAGRAPH_SEQUENCE)) 
		{
			shiftUpInBufferWith1();
			m_state.sArBuffer[m_state.sArBuffer.length - 1] = ""; // leave space for newline
		}
		
		// and you need to shift once anyway
		shiftUpInBufferWith1();
		m_state.sArBuffer[m_state.sArBuffer.length - 1] = sNewString;


		StringBuffer sConstructed = new StringBuffer();
		final boolean isCenterOutput = true;
		if (isCenterOutput)
			constructCenterOutput(sConstructed);
		else
			constructLeftOutput(sConstructed);
		// System.out.println(sConstructed);
		return sConstructed.toString();
	}


	/**
	 * shift the words in the buffer (up)
	 */
	private void shiftUpInBufferWith1()
	{
		String[] buffSource = m_state.sArBuffer;
		m_state.lBufferLineCounter++;
		
		int iNumberOfLinesAfterWhichGapIsEnabled = 20 + 1;
		boolean bShiftWithGap = buffSource.length >= iNumberOfLinesAfterWhichGapIsEnabled;
		
		if (bShiftWithGap) {
			
			String[] buffDestination = new String[buffSource.length];
			buffDestination[buffSource.length - 1] = buffSource[buffSource.length - 1];
			for (int i = buffSource.length - 1; i >= 2 ; i-=2) {
				buffDestination[i - 2] = buffSource[i];
				if (buffDestination[i - 2] == null)
					buffDestination[i - 2] = "";
				if (buffDestination[i - 1] == null)
					buffDestination[i - 1] = "";
			}
			m_state.sArBuffer = buffDestination;			
		}
		else {
			
			for (int i = 1; i < buffSource.length; i++) {
				buffSource[i - 1] = buffSource[i];
				if (buffSource[i - 1] == null)
					buffSource[i - 1] = "";
			}			
		}		
	}


	/**
	 * @param sConstructed
	 */
	private void constructCenterOutput(StringBuffer sConstructed)
	{
		String[] sArBuffer = m_state.sArBuffer;
		
		// 2. construct output
		for (int iIndex = 0; iIndex < sArBuffer.length; iIndex++) {
			final String str = sArBuffer[iIndex];
			
			// add extra space as a visual aid
			if (ADD_EXTRA_SEPARATION_IN_THE_LAST_LINE && 
					iIndex == sArBuffer.length-1)
				sConstructed.append("\n");
			
			// add space so that is centered
			final int iSpacesBefore = calculateSpacesBeforeWord(str);
			for (int iIndent = 0; iIndent < iSpacesBefore; iIndent++)
				sConstructed.append(" ");
			
			//add text
			sConstructed.append(str.trim());
			// if not the last line add new line
			if (iIndex < sArBuffer.length-1)
				sConstructed.append("\n");
		}
	}


	/**
	 * @param str
	 * @return
	 */
	private int calculateSpacesBeforeWord(final String str)
	{
		// initialize
		TextArea wordDisplay2 = new TextArea("", m_app.iDisplayNumberOfLines +1, 30);
		wordDisplay2.setFont(m_app.fontBigDisplay);		
		wordDisplay2.setText(str);
		
		// calculate word width
		FontMetrics fontMetrics = wordDisplay2.getFontMetrics(m_app.fontBigDisplay); 
		int iWordWidth = fontMetrics.stringWidth(str);

		// calculate standard space width
		final String space = " ";		
//		StringBuffer strStandardWithSpace = new StringBuffer();
//		for(int i = 0; i < 1.5 * minWordLength; i++)
//			strStandardWithSpace.append(space);		
//		int iStandardWith = fontMetrics.stringWidth(strStandardWithSpace.toString());
		float iStandardWith = (float) m_app.wordDisplay.getWidth() / 2;
		
		// calculate numbers of spaces needed
		float fDistanceWith = iStandardWith - iWordWidth / 2; 		
		return (int) (fDistanceWith / fontMetrics.stringWidth(space));
	}


	/**
	 * @param sConstructed
	 */
	private void constructLeftOutput(StringBuffer sConstructed)
	{
		String[] sArBuffer = m_state.sArBuffer;
		
		// 2. construct output
		for (int iIndex = 0; iIndex < sArBuffer.length; iIndex++) {

			// add extra space as a visual aid
			if (ADD_EXTRA_SEPARATION_IN_THE_LAST_LINE && 
					iIndex == sArBuffer.length-1)
				sConstructed.append("\n");
			
			// add space
			for (int iIndent = 0; iIndent < iIndex; iIndent++)
				sConstructed.append("     ");
			
			//add text
			sConstructed.append(sArBuffer[iIndex]);
			sConstructed.append("\n");
		}
	}

	/**
	 * @param text
	 * @param iCurrentTextPositionChars
	 * @param iWordEnd
	 * @return
	 */
	private String convertTextFoundToPrintable(String text,
			int iCurrentTextPositionChars, int iWordEnd) {
		// Get the word(s) and convert any non printable chars to
		// spaces.
		StringBuffer words = new StringBuffer(text.substring(
				iCurrentTextPositionChars, iWordEnd));
		for (int n = 0; n < words.length(); n++)
			if (words.charAt(n) < ' ' || words.charAt(n) == '\r'
					|| words.charAt(n) == '\n')
				words.setCharAt(n, '|');
		return words.toString();
	}

	

	/**
	 * @param text
	 * @param iPos
	 * @return
	 */
	private static boolean isBrakeCharacter(String text, int iPos) {
		
		// check if character is should be broken
		final boolean isBrakeCharacter = 
			text.charAt(iPos) < ' '
				|| text.charAt(iPos) == ','
				|| text.charAt(iPos) == '.'
				|| text.charAt(iPos) == ';'
				|| text.charAt(iPos) == ':'
				|| text.charAt(iPos) == '?'
				|| text.charAt(iPos) == '!'
				|| text.charAt(iPos) == '\r'
				|| text.charAt(iPos) == '\n'
				|| text.charAt(iPos) == '"'
				|| text.charAt(iPos) == '('
				|| text.charAt(iPos) == ')';
		return isBrakeCharacter;
	}


	/**
	 * 
	 */
	public void pageDown()
	{
//		System.out.println("pageDown called");
		
		// 1. get current position (which should be on end of text)
		// 2. call display lines times find next word
		// 3. display text
		String text = m_app.getTextInput().getText();
		int iCurrentTextPositionChars = m_app.getTextInput().getCaretPosition();
		iCurrentTextPositionChars += m_app.getTextInput().getSelectedText().length();
		long lRemeberBufferCounter = m_state.lBufferLineCounter;
		
		for (int i = 0; (m_state.lBufferLineCounter - lRemeberBufferCounter + 1) < m_state.sArBuffer.length - 1; i++) {
			int iWordEnd = displayNextWord(text, iCurrentTextPositionChars, false);

			// update end position
			if (iCurrentTextPositionChars >= iWordEnd) {
				break;
			}
			else {
				iCurrentTextPositionChars = iWordEnd;
			}
//			// pre-read
//			try
//			{
//				Thread.sleep(150);
//			} catch (InterruptedException e) {  }
		}
		m_taWordDisplay.requestFocus();
	}

	/**
	 * @throws InterruptedException 
	 * 
	 */
	public void pageUp() 
	{
		// 1. get current position (which should be on end of text)
		// 2. call display lines times find next word
		// 3. display text
		String text = m_app.getTextInput().getText();
		m_app.getTextInput().requestFocus();
		int iCurrentTextPositionChars = m_app.getTextInput().getCaretPosition();
		iCurrentTextPositionChars += m_app.getTextInput().getSelectedText().length();

		for (int i = 0; i < m_state.sArBuffer.length; i++) {
			iCurrentTextPositionChars = displayPreviousWord(text, iCurrentTextPositionChars);
			// TODO remove after debug
			try	{ Thread.sleep(1000); } catch (InterruptedException e)	{}
		}
	}


	/**
	 * @param text
	 * @param iTextPos
	 */
	private int displayPreviousWord(String text, int iTextPos)
	{
		// TODO debug
		System.out.println("displayPreviousWord called");
		iTextPos = findSelectionBegining(text, iTextPos, m_state.sArBuffer);
		iTextPos = findPreviousSelectionEnd(text, iTextPos, m_iMinWordLength);
		
		displayNextWord(text, iTextPos, false);
		return iTextPos;
	}

	/**
	 * @param text
	 * @param iTextPos
	 * @return
	 */
	protected static int findPreviousSelectionEnd(String text, int iTextPos,
													int iMinWordLength)
	{
		// TODO Auto-generated method stub
		iTextPos -= iMinWordLength;
		return iTextPos;
	}

	/**
	 * @param text
	 * @param iTextLength
	 * @param iTextPos
	 * @return
	 */
	protected static int findNextSelectionEnd(String text, int iTextLength,
			int iTextPos, int iMinWordLength) {
		// Find end of current word
		int iWordEnd = iTextPos + 2;
		

		// look for first limit
		while(true) {
			
			// end reached
			if (iWordEnd >= iTextLength - 1)
				break;
			
			if (iWordEnd - iTextPos >= iMinWordLength)
				break;
			
			if (isBrakeCharacter(text, iWordEnd))
				break;
			
			iWordEnd++;
		}
	
		// look for space or enter
		while(true) {
			
			// end reached
			if (iWordEnd >= iTextLength - 1)
				break;
			
			// brake only when space or enter is found at this point
			if (text.charAt(iWordEnd) == ' ' || text.charAt(iWordEnd) == '\n')
			{
				while (((text.charAt(iWordEnd) == ' ' || text.charAt(iWordEnd) == '\n'))
						&& (iWordEnd < iTextLength - 1))
				{
					iWordEnd++;
				}
				break;
			}

			
			iWordEnd++;
		}
			
		// normalize word end
		if (iWordEnd >= iTextLength - 1)
			iWordEnd = iTextLength - 1;
		
		return iWordEnd;
	}

	// private int skipWhiteSpaces(String text, int textLength,
	// int iCurrentTextPositionChars) {
	// // Skip leading whitespace
	// while (iCurrentTextPositionChars < textLength &&
	// text.charAt(iCurrentTextPositionChars) <= ' ')
	// iCurrentTextPositionChars++;
	// return iCurrentTextPositionChars;
	// }
}
